package net.bithappens.hnqe.mapper.graphxml;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import net.bithappens.hnqe.graph.EntryValue;
import net.bithappens.hnqe.graph.GraphNode;
import net.bithappens.hnqe.graph.EntryProperty;
import net.bithappens.hnqe.graph.GraphRelation;
import net.bithappens.hnqe.graph.GraphRelationMember;
import net.bithappens.hnqe.graph.GraphValue;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XMLGraphDataParser{

	private Logger log = Logger.getLogger(this.getClass());

	private String xmlFilePath;
		
	private Date xmlDataTimeStamp;
	
	private SimpleDateFormat simpleDateFormater;
	
	private NumberFormat numberFormater;
	
	private Set<GraphRelationMember> nodesToWrite;
	
	private Set<GraphRelation> relationsToWrite;
	
	
	
	
	public XMLGraphDataParser(String xmlFilePath){
		
		this.xmlFilePath = xmlFilePath;
				
	}
	
	public XMLGraphDataParser(String xmlFilePath, Date date){
		
		this.xmlFilePath = xmlFilePath;
		
		this.xmlDataTimeStamp = date;
				
	}
	
	
	public void parse() throws Exception {
		
		File xmlFile = new File(xmlFilePath);
		
		URL url = ClassLoader.getSystemResource("graphDataSchema.xsd");
		
		File xmlSchemaFile = new File(url.toURI());
		
		nodesToWrite = new HashSet<GraphRelationMember>();
		
		relationsToWrite = new HashSet<GraphRelation>();
				
		if (XMLGraphValidator.validate(xmlFile, xmlSchemaFile)){
			
			try {
				
				DocumentBuilder documentBuilder;
				
				documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
				
				Document doc = documentBuilder.parse(xmlFile);
				
				doc.getDocumentElement().normalize();
	
				
				Locale locale = new Locale(doc.getDocumentElement().getAttribute("locale"));
				
				numberFormater = NumberFormat.getInstance(locale);

				String datePattern = doc.getDocumentElement().getAttribute("datePattern");
				
				simpleDateFormater = new SimpleDateFormat(datePattern);

				
				Date graphCreation = simpleDateFormater.parse(doc.getDocumentElement().getAttribute("timeStamp"));
				
				if(xmlDataTimeStamp == null || graphCreation.after(xmlDataTimeStamp)){
				
					xmlDataTimeStamp = graphCreation;
					
					Map<URI, GraphRelationMember> nodes = parseNodes(doc.getElementsByTagName("node"));
					
					nodes.putAll(parseValueNodes(doc.getElementsByTagName("valueNode")));
					
					relationsToWrite = parseRelations(doc.getElementsByTagName("hyperEdge"), nodes);
										
				} else {
					
					log.info("Nothing changed in graph data so far");
					
				}
					
			} catch (Exception e) {
				log.error("Data couldn't be loaded due to: "+e.getMessage(), e);
				
				nodesToWrite = new HashSet<GraphRelationMember>();
				
				relationsToWrite = new HashSet<GraphRelation>();
			}	
		}		
	}


	private Map<URI, GraphRelationMember> parseNodes(NodeList nodeList) throws Exception {
		
		Map<URI,GraphRelationMember> returnMap = new HashMap<URI, GraphRelationMember>();
		
		for (int s = 0; s < nodeList.getLength(); s++) {
			
			Node node = nodeList.item(s);
			
			String uri = node.getAttributes().getNamedItem("uri").getNodeValue();
			
			String nodeClass = node.getAttributes().getNamedItem("nodeType").getNodeValue();
			
			Date lease = getLease(node.getAttributes().getNamedItem("lease"));
			
			GraphNode graphNode = new GraphNode(nodeClass, new URI(uri), lease);
			
			
			NodeList properties = node.getChildNodes();
			
			for (int t = 0; t < properties.getLength(); t++) {
			
				Node property = properties.item(t);
				
				if(property.getNodeType() == Node.ELEMENT_NODE){
					
					String name = property.getAttributes().getNamedItem("name").getNodeValue();
					
					String type = property.getAttributes().getNamedItem("type").getNodeValue();
					
					EntryValue entryValue = getValueInstance(type, property.getChildNodes());
					
					EntryProperty graphProperty = new EntryProperty(name, entryValue);
					
					graphNode.addProperty(graphProperty);
										
				}
			}
			
			returnMap.put(graphNode.getUri(), graphNode);
			
			nodesToWrite.add(graphNode);
		}
		
		return returnMap;
	}


	private Map<URI, GraphRelationMember> parseValueNodes(NodeList valueNodeList) throws Exception {

		Map<URI,GraphRelationMember> returnMap = new HashMap<URI, GraphRelationMember>();
		
		for (int s = 0; s < valueNodeList.getLength(); s++) {
			
			Node node = valueNodeList.item(s);
			
			String uri = node.getAttributes().getNamedItem("uri").getNodeValue();
			
			Date lease = getLease(node.getAttributes().getNamedItem("lease"));
			
			String type = node.getAttributes().getNamedItem("type").getNodeValue();
			
			GraphValue graphValue = new GraphValue(new URI(uri), lease, getValueInstance(type, node.getChildNodes()));
			
			returnMap.put(graphValue.getUri(), graphValue);
			
			nodesToWrite.add(graphValue);
		}
		
		return returnMap;
	}

		
	
	
	
	private Set<GraphRelation> parseRelations(NodeList nodeList, Map<URI, GraphRelationMember> nodes) throws Exception {
		
		Set<GraphRelation> toReturn = new HashSet<GraphRelation>();
		
		for (int s = 0; s < nodeList.getLength(); s++) {
			
			Node node = nodeList.item(s);
			
			String uri = node.getAttributes().getNamedItem("uri").getNodeValue();
			
			String relationType = node.getAttributes().getNamedItem("relationType").getNodeValue();
			
			Date lease = getLease(node.getAttributes().getNamedItem("lease"));
			
			GraphRelation graphRelation = new GraphRelation(relationType, new URI(uri), lease); 
			
			NodeList relationEntries = node.getChildNodes();
			
			for (int t = 0; t < relationEntries.getLength(); t++) {
			
				Node item = relationEntries.item(t);
				
				if(item.getNodeType() == Node.ELEMENT_NODE){
					
					if("property".equals(item.getNodeName())){
						
						String proptertyName = item.getAttributes().getNamedItem("name").getNodeValue();
						
						String type = item.getAttributes().getNamedItem("type").getNodeValue();
						
						EntryValue entryValue = getValueInstance(type, item.getChildNodes());
						
						EntryProperty graphProperty = new EntryProperty(proptertyName, entryValue);
						
						graphRelation.addProperty(graphProperty);
						
						continue;
					}
					
					if("nodeRef".equals(item.getNodeName())){
						
						URI refUri = new URI(item.getAttributes().getNamedItem("nodeUri").getNodeValue());
						
						graphRelation.addEntry(nodes.get(refUri));
						
						nodesToWrite.remove(nodes.get(refUri));
						
						continue;
						
					} 
				}
			}
			
			toReturn.add(graphRelation);
		}
		
		return toReturn;
	}

	
	private Date getLease(Node leaseNode) throws ParseException {
		
		Date lease = null;
		
		if(leaseNode != null){

			String leaseString = leaseNode.getNodeValue();
		
			if(leaseString != null && !leaseString.isEmpty()){
				
				lease = simpleDateFormater.parse(leaseString);
				
			}
		}
		return lease;
	}
	
	private EntryValue getValueInstance(String type, NodeList nodeList) throws Exception {
		
		String valueString = "";
		
		for (int s = 0; s < nodeList.getLength(); s++) {
			valueString += nodeList.item(s).getNodeValue();		
		}
		
		if("String".equals(type)){
			return EntryValue.createEntryValue(valueString);
		}
		
		if("Integer".equals(type)){
			return EntryValue.createEntryValue(Integer.valueOf(valueString));
		}
		
		if("Double".equals(type)){
			return EntryValue.createEntryValue(Double.valueOf(valueString));
		}
		
		if("Boolean".equals(type)){
			return EntryValue.createEntryValue(Boolean.valueOf(valueString));
		}
		
		if("Float".equals(type)){
			return EntryValue.createEntryValue(Float.valueOf(valueString));
		}
		
		if("Number".equals(type)){
			return EntryValue.createEntryValue(numberFormater.parse(valueString));
		}
		
		if("Date".equals(type)){
			return EntryValue.createEntryValue(simpleDateFormater.parse(valueString));
		} 		
		
		return null;
	}


	public Set<GraphRelationMember> getNodesToWrite() {
		return nodesToWrite;
	}

	public Set<GraphRelation> getRelationsToWrite() {
		return relationsToWrite;
	}

	public Date getXmlDataTimeStamp() {
		return xmlDataTimeStamp;
	}
	
}
